/* global travelgoFlightSearch */
(function () {
var cfg = typeof travelgoFlightSearch === "object" && travelgoFlightSearch ? travelgoFlightSearch : {};
var API_KEY = cfg.apiKey || "";
var BASE_URL = cfg.baseUrl || "https://www.searchapi.io/api/v1/search";
var REST_URL = String(cfg.restUrl || "").replace(/\/$/, "");
var AIRPORTS_URL = String(cfg.airportsUrl || "").replace(/\/$/, "");
var MAPS_NEARBY_URL = String(cfg.mapsNearbyUrl || "").replace(/\/$/, "");
var HAS_GOOGLE_MAPS = !!cfg.hasGoogleMaps;
var useRestProxy = REST_URL.length > 0;
var S = cfg.strings || {};
var PRICING = cfg.pricing || {};
var RESULTS_PAGE_URL = String(cfg.resultsPageUrl || "").trim();
var AJAX_URL = String(cfg.ajaxUrl || "");
var BOOKING_NONCE = String(cfg.bookingNonce || "");
var BOOKING_ENABLED = !!cfg.bookingEnabled;
var AIRLINE_LOGOS_URI = String(cfg.airlineLogosUri || "").replace(/\/$/, "");
var BUNDLED_AIRLINE_ICAO = Array.isArray(cfg.bundledAirlineIcao) ? cfg.bundledAirlineIcao : [];
var REMOTE_AIRLINE_LOGO_URL = String(cfg.remoteAirlineLogoUrl || "https://images.kiwi.com/airlines/64/%s.png");
var ASSET_VERSION = String(cfg.assetVersion || "");
if (!useRestProxy && !API_KEY) return;
var homeRoot = document.getElementById("travelgo-skyscout-root");
var resultsRoot = document.getElementById("travelgo-flight-results-root");
var isHome = !!(homeRoot && homeRoot.getAttribute("data-has-key") === "1");
var isResults = !!(
resultsRoot && resultsRoot.getAttribute("data-has-key") === "1"
);
if (!isHome && !isResults) return;
var ROOT = resultsRoot || homeRoot;
function $(id) {
return ROOT ? ROOT.querySelector("#" + id) : null;
}
/** Results list / price insight / sort (flight results page only) */
function res$(suffix) {
return resultsRoot
? resultsRoot.querySelector("#travelgo-ss-" + suffix)
: null;
}
var state = {
tripType: "one_way",
from: "DEL",
to: "BOM",
departDate: "",
returnDate: "",
adults: "1",
children: "0",
infants: "0",
travelClass: "economy",
stops: "any",
currency: "INR",
sortBy: "top_flights",
fareType: "regular",
currentResults: [],
allFlights: [],
currentMeta: null,
lastRenderedFlights: null,
};
var filterState = {
stopsNonstop: false,
stopsOne: false,
stopsTwoPlus: false,
depBefore6: false,
dep6to12: false,
dep12to18: false,
depAfter18: false,
priceMaxUser: null,
airlinesAllowed: null,
};
var filtersWired = false;
var fareCalLoading = false;
var fareCalCache = {};
var popoverDraft = {
adults: "1",
children: "0",
infants: "0",
travelClass: "economy",
stops: "any",
};
var airportNames = {
DEL: "Indira Gandhi International Airport",
BOM: "Chhatrapati Shivaji International Airport",
MAA: "Chennai International Airport",
BLR: "Kempegowda International Airport",
HYD: "Rajiv Gandhi International Airport",
CCU: "Netaji Subhas Chandra Bose International Airport",
COK: "Cochin International Airport",
TRV: "Trivandrum International Airport",
JFK: "John F. Kennedy International Airport",
LHR: "Heathrow Airport",
DXB: "Dubai International Airport",
SIN: "Singapore Changi Airport",
BKK: "Suvarnabhumi Airport",
KUL: "Kuala Lumpur International Airport",
CDG: "Charles de Gaulle Airport",
AMS: "Schiphol Airport",
MAD: "Adolfo Suárez Madrid–Barajas Airport",
FCO: "Leonardo da Vinci–Fiumicino Airport",
NRT: "Narita International Airport",
SYD: "Sydney Kingsford Smith Airport",
LAX: "Los Angeles International Airport",
ORD: "O'Hare International Airport",
PNQ: "Pune Airport",
GOI: "Goa International Airport",
IXC: "Chandigarh International Airport",
IXR: "Birsa Munda Airport",
GAU: "Lokpriya Gopinath Bordoloi International Airport",
JAI: "Jaipur International Airport",
AMD: "Sardar Vallabhbhai Patel International Airport",
ATQ: "Sri Guru Ram Dass Jee International Airport",
IXB: "Bagdogra Airport",
IXJ: "Jammu Airport",
IXU: "Aurangabad Airport",
NAG: "Dr. Babasaheb Ambedkar International Airport",
LKO: "Chaudhary Charan Singh International Airport",
IXM: "Madurai Airport",
IXS: "Silchar Airport",
IXG: "Belagavi Airport",
BBI: "Biju Patnaik International Airport",
PAT: "Jay Prakash Narayan Airport",
IXA: "Agartala Airport",
DIB: "Dibrugarh Airport",
CCJ: "Calicut International Airport",
CNN: "Kannur International Airport",
TRZ: "Tiruchirappalli International Airport",
VNS: "Lal Bahadur Shastri Airport",
IXD: "Prayagraj Airport",
STV: "Surat Airport",
RPR: "Swami Vivekananda Airport",
BDQ: "Vadodara Airport",
IXE: "Mangalore International Airport",
RUH: "King Khalid International Airport",
JED: "King Abdulaziz International Airport",
DOH: "Hamad International Airport",
AUH: "Zayed International Airport",
SHJ: "Sharjah International Airport",
KWI: "Kuwait International Airport",
MCT: "Muscat International Airport",
CAI: "Cairo International Airport",
ADD: "Bole International Airport",
JNB: "O. R. Tambo International Airport",
CPT: "Cape Town International Airport",
};
var airportCity = {
DEL: "Delhi",
BOM: "Mumbai",
MAA: "Chennai",
BLR: "Bengaluru",
HYD: "Hyderabad",
CCU: "Kolkata",
COK: "Kochi",
TRV: "Thiruvananthapuram",
JFK: "New York",
LHR: "London",
DXB: "Dubai",
SIN: "Singapore",
BKK: "Bangkok",
KUL: "Kuala Lumpur",
CDG: "Paris",
AMS: "Amsterdam",
MAD: "Madrid",
FCO: "Rome",
NRT: "Tokyo",
SYD: "Sydney",
LAX: "Los Angeles",
ORD: "Chicago",
PNQ: "Pune",
GOI: "Goa",
IXC: "Chandigarh",
IXR: "Ranchi",
GAU: "Guwahati",
JAI: "Jaipur",
AMD: "Ahmedabad",
ATQ: "Amritsar",
IXB: "Bagdogra",
IXJ: "Jammu",
IXU: "Aurangabad",
NAG: "Nagpur",
LKO: "Lucknow",
IXM: "Madurai",
IXS: "Silchar",
IXG: "Belagavi",
BBI: "Bhubaneswar",
PAT: "Patna",
IXA: "Agartala",
DIB: "Dibrugarh",
CCJ: "Kozhikode",
CNN: "Kannur",
TRZ: "Tiruchirappalli",
VNS: "Varanasi",
IXD: "Prayagraj",
STV: "Surat",
RPR: "Raipur",
BDQ: "Vadodara",
IXE: "Mangalore",
RUH: "Riyadh",
JED: "Jeddah",
DOH: "Doha",
AUH: "Abu Dhabi",
SHJ: "Sharjah",
KWI: "Kuwait City",
MCT: "Muscat",
CAI: "Cairo",
ADD: "Addis Ababa",
JNB: "Johannesburg",
CPT: "Cape Town",
};
var AIRPORT_LIST = [];
Object.keys(airportCity).forEach(function (k) {
if (k.length === 3 && /^[A-Z]{3}$/.test(k)) {
AIRPORT_LIST.push({
iata: k,
city: airportCity[k],
name: airportNames[k] || "",
});
}
});
function upsertAirportList(items) {
if (!Array.isArray(items) || !items.length) return;
var seen = {};
AIRPORT_LIST.forEach(function (a) {
var code = String(a.iata || "").toUpperCase();
if (code) seen[code] = true;
});
items.forEach(function (a) {
var code = String(a && a.iata ? a.iata : "").toUpperCase().replace(/[^A-Z]/g, "").substring(0, 3);
if (!code || seen[code]) return;
AIRPORT_LIST.push({
iata: code,
city: String((a && a.city) || code),
name: String((a && a.name) || "")
});
if (!airportCity[code]) airportCity[code] = String((a && a.city) || code);
if (!airportNames[code]) airportNames[code] = String((a && a.name) || "");
seen[code] = true;
});
}
async function fetchAirportSuggestionsRemote(raw) {
if (!AIRPORTS_URL) return [];
var q = String(raw || "").trim();
if (q.length < 2) return [];
try {
var url = AIRPORTS_URL + "?search=" + encodeURIComponent(q);
var res = await fetch(url, { credentials: "same-origin" });
if (!res.ok) return [];
var data = await res.json();
var arr = data && Array.isArray(data.airports) ? data.airports : [];
upsertAirportList(arr);
return arr;
} catch (e) {
return [];
}
}
function resolveAirportQuery(raw) {
var q = String(raw || "")
.trim()
.toLowerCase()
.replace(/\s+/g, " ");
if (!q) return null;
var aliases = {
trivandrum: "TRV",
bangalore: "BLR",
bengaluru: "BLR",
bombay: "BOM",
mumbai: "BOM",
calcutta: "CCU",
kolkata: "CCU",
cochin: "COK",
kochi: "COK",
madras: "MAA",
chennai: "MAA",
calicut: "CCJ",
kozhikode: "CCJ",
};
if (aliases[q]) return aliases[q];
var alnum = q.replace(/[^a-z0-9]/g, "");
if (/^[a-z]{3}$/.test(alnum)) {
var c3 = alnum.toUpperCase();
if (airportCity[c3]) return c3;
}
var best = null;
var score = 0;
for (var i = 0; i < AIRPORT_LIST.length; i++) {
var a = AIRPORT_LIST[i];
var city = a.city.toLowerCase();
var name = (a.name || "").toLowerCase();
var code = a.iata.toLowerCase();
var s = 0;
if (code === q || code === alnum) s = 100;
else if (city === q) s = 95;
else if (name === q) s = 93;
else if (city.indexOf(q) === 0) s = 82;
else if (city.indexOf(q) !== -1) s = 65;
else if (name.indexOf(q) !== -1) s = 58;
else if (q.length >= 2 && alnum.length >= 2 && code.indexOf(alnum) === 0)
s = 45;
if (s > score) {
score = s;
best = a.iata;
}
}
return score >= 45 ? best : null;
}
function airportMatchesQuery(a, q) {
if (!q) return true;
var qq = q.toLowerCase();
var code = (a.iata || "").toLowerCase();
var city = (a.city || "").toLowerCase();
var name = (a.name || "").toLowerCase();
return (
code.indexOf(qq) === 0 ||
city.indexOf(qq) !== -1 ||
name.indexOf(qq) !== -1
);
}
function airportSuggestions(raw, limit) {
var q = String(raw || "").trim();
var out = AIRPORT_LIST.filter(function (a) {
return airportMatchesQuery(a, q);
});
out.sort(function (a, b) {
var aq = q.toLowerCase();
var ac = (a.iata || "").toLowerCase();
var bc = (b.iata || "").toLowerCase();
if (aq) {
var aStarts = ac.indexOf(aq) === 0 ? 0 : 1;
var bStarts = bc.indexOf(aq) === 0 ? 0 : 1;
if (aStarts !== bStarts) return aStarts - bStarts;
}
return (a.city || "").localeCompare(b.city || "");
});
return out.slice(0, limit || 10);
}
var classLabels = {
economy: "Economy",
premium_economy: "Premium economy",
business: "Business",
first_class: "First class",
};
function metaLine(code) {
var c = code.toUpperCase().substring(0, 3);
var n = airportNames[c];
return n ? c + ", " + n : c + " Airport";
}
function cityLine(code) {
var c = code.toUpperCase().substring(0, 3);
return airportCity[c] || c;
}
function applyPricingToFare(rawFare) {
var base = Number(rawFare || 0);
if (!isFinite(base) || base < 0) base = 0;
var markupPercent = Number(PRICING.markupPercent || 0);
var convenienceFee = Number(PRICING.convenienceFee || 0);
var couponType = String(PRICING.couponType || "none");
var couponValue = Number(PRICING.couponValue || 0);
var couponCode = String(PRICING.couponCode || "");
var cancelType = String(PRICING.cancelType || "none");
var cancelValue = Number(PRICING.cancelValue || 0);
var refundNote = String(PRICING.refundNote || "");
var markupAmount = base * (markupPercent / 100);
var gross = Math.max(0, base + markupAmount + Math.max(0, convenienceFee));
var couponAmount = 0;
if (couponType === "percent") couponAmount = gross * (couponValue / 100);
if (couponType === "fixed") couponAmount = couponValue;
couponAmount = Math.min(Math.max(0, couponAmount), gross);
var cancelAmount = 0;
if (cancelType === "percent") cancelAmount = gross * (cancelValue / 100);
if (cancelType === "fixed") cancelAmount = cancelValue;
cancelAmount = Math.min(Math.max(0, cancelAmount), gross);
return {
baseFare: Math.round(base * 100) / 100,
markupPercent: markupPercent,
markupAmount: Math.round(markupAmount * 100) / 100,
convenienceFee: Math.round(Math.max(0, convenienceFee) * 100) / 100,
grossTotal: Math.round(gross * 100) / 100,
couponType: couponType,
couponValue: couponValue,
couponCode: couponCode,
couponAmount: Math.round(couponAmount * 100) / 100,
cancelType: cancelType,
cancelValue: cancelValue,
cancelAmount: Math.round(cancelAmount * 100) / 100,
estimatedRefund: Math.round(Math.max(0, gross - cancelAmount) * 100) / 100,
refundNote: refundNote,
};
}
function formatClockTime(raw) {
var s = String(raw || "").trim();
if (!s) return "--:--";
var m = s.match(/(\d{1,2}):(\d{2})/);
if (!m) return s;
var hh = ("0" + m[1]).slice(-2);
var mm = m[2];
return hh + ":" + mm;
}
function pad2(n) {
return ("0" + Number(n)).slice(-2);
}
/**
* Canonical YYYY-MM-DD for search + segment labels.
* Accepts HTML date input, DD-MM-YYYY / DD/MM/YYYY (day-first when ambiguous), and ISO.
*/
function normalizeSearchDateToIso(raw) {
var s = String(raw || "").trim();
if (!s) return "";
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
var m = s.match(/^(\d{1,2})[\/.\-](\d{1,2})[\/.\-](\d{4})$/);
if (m) {
var p1 = parseInt(m[1], 10);
var p2 = parseInt(m[2], 10);
var y = parseInt(m[3], 10);
var day;
var month;
if (p1 > 12) {
day = p1;
month = p2;
} else if (p2 > 12) {
month = p1;
day = p2;
} else {
day = p1;
month = p2;
}
if (y < 1900 || month < 1 || month > 12 || day < 1 || day > 31) return "";
return y + "-" + pad2(month) + "-" + pad2(day);
}
var d = new Date(s);
if (!isNaN(d.getTime())) {
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
}
return "";
}
/** Split API time into Y-m-d (if present) and HH:MM clock. */
function parseAirportDateTime(raw) {
var s = String(raw || "").trim();
if (!s) return { iso: "", clock: "" };
var m = s.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{1,2}:\d{2})/);
if (m) return { iso: m[1], clock: formatClockTime(m[2]) };
m = s.match(/^(\d{4})[\/](\d{2})[\/](\d{2})[ T](\d{1,2}:\d{2})/);
if (m) return { iso: m[1] + "-" + m[2] + "-" + m[3], clock: formatClockTime(m[4]) };
return { iso: "", clock: formatClockTime(s) };
}
function clockToMins(raw) {
var m = String(raw || "").match(/(\d{1,2}):(\d{2})/);
if (!m) return null;
return parseInt(m[1], 10) * 60 + parseInt(m[2], 10);
}
/** Short date (e.g. "14 Apr") + time for segment rows. */
function formatDateWithTime(isoDate, rawTimeForClock) {
var t = formatClockTime(rawTimeForClock);
var iso = normalizeSearchDateToIso(isoDate);
if (!iso) return t || "--:--";
var d = new Date(iso + "T12:00:00");
if (isNaN(d.getTime())) return t;
var datePart = d.toLocaleDateString("en-IN", { day: "numeric", month: "short" });
return datePart + " " + t;
}
function normalizeCurrency(raw) {
var c = String(raw || "").toUpperCase();
return c === "USD" ? "USD" : "INR";
}
function currencyLocale(c) {
return c === "USD" ? "en-US" : "en-IN";
}
function currencySymbol(c) {
return c === "USD" ? "$" : "₹";
}
function formatMoney(amount) {
var n = Number(amount || 0);
if (!isFinite(n)) n = 0;
return currencySymbol(state.currency) + n.toLocaleString(currencyLocale(state.currency));
}
function addDaysIso(iso, days) {
var norm = normalizeSearchDateToIso(iso);
if (!norm) return String(iso || "");
var d = new Date(norm + "T12:00:00");
if (isNaN(d.getTime())) return norm;
d.setDate(d.getDate() + days);
return d.toISOString().split("T")[0];
}
function diffDaysIso(a, b) {
var na = normalizeSearchDateToIso(a);
var nb = normalizeSearchDateToIso(b);
if (!na || !nb) return 0;
var da = new Date(na + "T12:00:00");
var db = new Date(nb + "T12:00:00");
if (isNaN(da.getTime()) || isNaN(db.getTime())) return 0;
return Math.round((db.getTime() - da.getTime()) / 86400000);
}
function fmtFareCalDay(iso) {
var d = new Date(iso + "T12:00:00");
if (isNaN(d.getTime())) return iso;
var w = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var m = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
return w[d.getDay()] + ", " + m[d.getMonth()] + " " + d.getDate();
}
function parseLowestPriceFromData(data) {
if (!data || typeof data !== "object") return null;
var pi = data.price_insights && Number(data.price_insights.lowest_price || 0);
if (isFinite(pi) && pi > 0) return pi;
var prices = [];
var best = Array.isArray(data.best_flights) ? data.best_flights : [];
var other = Array.isArray(data.other_flights) ? data.other_flights : [];
best.concat(other).forEach(function (f) {
var p = Number(f && f.price ? f.price : 0);
if (isFinite(p) && p > 0) prices.push(p);
});
if (!prices.length) return null;
prices.sort(function (a, b) { return a - b; });
return prices[0];
}
async function fetchFareForDate(outDate, retDate) {
var apiTrip = state.tripType === "one_way" ? "one_way" : "round_trip";
var cacheKey =
state.from + "|" + state.to + "|" + outDate + "|" + (retDate || "") + "|" + apiTrip + "|" + state.travelClass + "|" + state.adults + "|" + state.children + "|any";
if (fareCalCache.hasOwnProperty(cacheKey)) return fareCalCache[cacheKey];
var payload = {
departure_id: state.from,
arrival_id: state.to,
outbound_date: outDate,
flight_type: apiTrip,
travel_class: state.travelClass,
adults: parseInt(state.adults, 10) || 1,
children: parseInt(state.children, 10) || 0,
// Use broadest stop filter for true cheapest-day signal.
stops: "any",
sort_by: "price",
currency: state.currency,
gl: "in",
hl: "en",
};
if (apiTrip === "round_trip" && retDate) payload.return_date = retDate;
try {
var data;
if (useRestProxy) {
var res = await fetch(REST_URL, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "same-origin",
body: JSON.stringify(payload),
});
if (!res.ok) {
fareCalCache[cacheKey] = null;
return null;
}
data = await res.json();
} else {
var params = new URLSearchParams({
engine: "google_flights",
api_key: API_KEY,
departure_id: state.from,
arrival_id: state.to,
outbound_date: outDate,
flight_type: apiTrip,
travel_class: state.travelClass,
adults: String(parseInt(state.adults, 10) || 1),
children: String(parseInt(state.children, 10) || 0),
stops: "any",
sort_by: "price",
currency: state.currency,
gl: "in",
hl: "en",
});
if (apiTrip === "round_trip" && retDate) params.set("return_date", retDate);
var resLegacy = await fetch(BASE_URL + "?" + params.toString());
if (!resLegacy.ok) {
fareCalCache[cacheKey] = null;
return null;
}
data = await resLegacy.json();
}
var lowest = parseLowestPriceFromData(data);
fareCalCache[cacheKey] = lowest;
return lowest;
} catch (e) {
fareCalCache[cacheKey] = null;
return null;
}
}
function syncResultsUrl() {
if (!isResults || !RESULTS_PAGE_URL) return;
var sep = RESULTS_PAGE_URL.indexOf("?") >= 0 ? "&" : "?";
var nextUrl = RESULTS_PAGE_URL + sep + buildTfQuery();
if (window.history && window.history.replaceState) {
window.history.replaceState({}, "", nextUrl);
}
}
async function renderFareCalendar() {
if (!isResults || fareCalLoading) return;
var host = document.getElementById("travelgo-fr-farecal-track");
if (!host || !state.departDate) return;
fareCalLoading = true;
var nights = 0;
if (state.tripType === "round_trip" && state.returnDate) {
nights = Math.max(1, diffDaysIso(state.departDate, state.returnDate));
}
var days = [];
for (var i = -3; i <= 3; i++) {
days.push(addDaysIso(state.departDate, i));
}
host.innerHTML = days
.map(function (d) {
return (
'' +
'' + fmtFareCalDay(d) + " " +
'… '
);
})
.join("");
var jobs = days.map(async function (d) {
var rd = state.tripType === "round_trip" && nights > 0 ? addDaysIso(d, nights) : "";
var p = await fetchFareForDate(d, rd);
return { d: d, p: p };
});
var rows = await Promise.all(jobs);
rows.forEach(function (row) {
var chip = host.querySelector('[data-fare-date="' + row.d + '"]');
if (!chip) return;
var priceEl = chip.querySelector(".travelgo-fr-farecal-price");
if (priceEl) {
priceEl.textContent = row.p != null ? formatMoney(row.p) : "N/A";
}
});
host.querySelectorAll(".travelgo-fr-farecal-chip").forEach(function (chip) {
chip.addEventListener("click", function () {
var d = chip.getAttribute("data-fare-date");
if (!d || d === state.departDate) return;
var oldDep = state.departDate;
state.departDate = d;
if (state.tripType === "round_trip" && state.returnDate) {
var span = Math.max(1, diffDaysIso(oldDep, state.returnDate));
state.returnDate = addDaysIso(d, span);
}
host.querySelectorAll(".travelgo-fr-farecal-chip").forEach(function (c) {
c.classList.toggle("is-active", c.getAttribute("data-fare-date") === d);
});
syncResultsUrl();
fetchFlightData();
renderFareCalendar();
});
});
fareCalLoading = false;
}
function updatePaxSummary() {
var total =
parseInt(state.adults, 10) +
parseInt(state.children, 10) +
parseInt(state.infants, 10);
var ps = $("travelgo-ss-pax-summary");
var cs = $("travelgo-ss-class-summary");
if (ps) {
ps.textContent =
total === 1
? S.oneTraveller || "1 Traveller"
: total + " " + (S.travellers || "Travellers");
}
if (cs) {
cs.textContent =
classLabels[state.travelClass] || state.travelClass;
}
}
function initDates() {
var today = new Date();
var depart = new Date(today);
depart.setDate(today.getDate() + 15);
var ret = new Date(depart);
ret.setDate(depart.getDate() + 7);
function fmt(d) {
return d.toISOString().split("T")[0];
}
var depEl = $("travelgo-ss-depart-date");
var retEl = $("travelgo-ss-return-date");
if (!depEl || !retEl) return;
depEl.value = fmt(depart);
retEl.value = fmt(ret);
depEl.min = fmt(today);
retEl.min = fmt(depart);
updateDateDisplays();
}
function updateDateDisplays() {
var depEl = $("travelgo-ss-depart-date");
var retEl = $("travelgo-ss-return-date");
var dv = depEl ? depEl.value : "";
var rv = retEl ? retEl.value : "";
if (dv) {
state.departDate = dv;
if (retEl) retEl.min = dv;
}
if (rv) {
state.returnDate = rv;
}
}
function updateAirport(field, code) {
var upperCode = String(code || "")
.toUpperCase()
.replace(/[^A-Z]/g, "")
.substring(0, 3);
if (upperCode.length < 3) return;
if (field === "from") {
state.from = upperCode;
var fd = $("travelgo-ss-from-display");
var fm = $("travelgo-ss-from-meta");
var fi = $("travelgo-ss-from-input");
if (fd) fd.textContent = cityLine(upperCode);
if (fm) fm.textContent = metaLine(upperCode);
if (fi) {
fi.value = upperCode;
fi.setAttribute("data-selected-code", upperCode);
}
} else {
state.to = upperCode;
var td = $("travelgo-ss-to-display");
var tm = $("travelgo-ss-to-meta");
var ti = $("travelgo-ss-to-input");
if (td) td.textContent = cityLine(upperCode);
if (tm) tm.textContent = metaLine(upperCode);
if (ti) {
ti.value = upperCode;
ti.setAttribute("data-selected-code", upperCode);
}
}
}
function wireAirportAutocomplete(inputId, ddId, field) {
var el = $(inputId);
var dd = $(ddId);
if (!el || !dd) return;
var fieldsRow = $("travelgo-ss-fields-row");
var loadingTimer = null;
var requestToken = 0;
function closeDd() {
if (loadingTimer) {
clearTimeout(loadingTimer);
loadingTimer = null;
}
dd.hidden = true;
dd.innerHTML = "";
if (fieldsRow) fieldsRow.classList.remove("travelgo-gb-bar--dd-open");
}
function renderDdLoading() {
dd.innerHTML =
'
' +
' ' +
'' +
escapeHtml(S.searching || "Searching…") +
"
";
dd.hidden = false;
if (fieldsRow) fieldsRow.classList.add("travelgo-gb-bar--dd-open");
}
function renderDd(items) {
if (!items.length) {
closeDd();
return;
}
dd.innerHTML = items
.map(function (a) {
return (
'' +
'' +
escapeHtml(a.iata) +
" " +
'' +
'' +
escapeHtml(a.city || a.iata) +
" " +
'' +
escapeHtml(a.name || "Airport") +
" "
);
})
.join("");
dd.hidden = false;
if (fieldsRow) fieldsRow.classList.add("travelgo-gb-bar--dd-open");
}
function pickAirport(iata) {
if (!iata) return;
updateAirport(field, iata);
closeDd();
if (document.activeElement === el) el.blur();
}
function validateSelection() {
var selected = el.getAttribute("data-selected-code") || "";
if (selected && selected.length === 3) {
updateAirport(field, selected);
return true;
}
var cur = field === "from" ? state.from : state.to;
updateAirport(field, cur);
return false;
}
el.addEventListener("focus", function () {
requestToken++;
var myToken = requestToken;
renderDdLoading();
Promise.resolve(fetchAirportSuggestionsRemote(el.value)).then(function () {
if (myToken !== requestToken) return;
renderDd(airportSuggestions(el.value, 10));
});
});
el.addEventListener("input", function () {
el.removeAttribute("data-selected-code");
renderDdLoading();
if (loadingTimer) clearTimeout(loadingTimer);
loadingTimer = setTimeout(function () {
loadingTimer = null;
requestToken++;
var myToken = requestToken;
Promise.resolve(fetchAirportSuggestionsRemote(el.value)).then(function () {
if (myToken !== requestToken) return;
renderDd(airportSuggestions(el.value, 10));
});
}, 120);
});
dd.addEventListener("mousedown", function (e) {
var btn = e.target.closest(".travelgo-gb-airport-opt");
if (!btn || !dd.contains(btn)) return;
// Select on mousedown so blur doesn't reset to the previous airport.
e.preventDefault();
var iata = btn.getAttribute("data-iata");
pickAirport(iata);
});
dd.addEventListener("click", function (e) {
var btn = e.target.closest(".travelgo-gb-airport-opt");
if (!btn || !dd.contains(btn)) return;
var iata = btn.getAttribute("data-iata");
pickAirport(iata);
});
el.addEventListener("blur", function () {
setTimeout(function () {
validateSelection();
closeDd();
}, 120);
});
document.addEventListener("click", function (e) {
if (e.target === el || dd.contains(e.target)) return;
closeDd();
});
}
function setTripType(type) {
if (!homeRoot) return;
state.tripType = type;
homeRoot.classList.toggle("travelgo-trip-one-way", type === "one_way");
homeRoot.classList.toggle("travelgo-trip-multi-city", type === "multi_city");
homeRoot.querySelectorAll(".travelgo-gb-trip-opt").forEach(function (b) {
var on = b.getAttribute("data-type") === type;
b.classList.toggle("is-active", on);
b.setAttribute("aria-selected", on ? "true" : "false");
var ch = b.querySelector(".travelgo-gb-trip-check");
if (ch) ch.textContent = on ? "✓" : "";
});
var rf = $("travelgo-ss-return-field");
var msg = $("travelgo-gb-multicity-msg");
var searchBtn = $("travelgo-ss-search");
if (msg) msg.hidden = type !== "multi_city";
if (rf) rf.hidden = type === "one_way" || type === "multi_city";
if (type === "one_way" || type === "multi_city") {
var retDate = $("travelgo-ss-return-date");
if (retDate) retDate.value = "";
state.returnDate = "";
}
if (searchBtn) searchBtn.disabled = type === "multi_city";
}
if (isHome && homeRoot) {
var hmCur = $("travelgo-ss-currency");
state.currency = normalizeCurrency((hmCur && hmCur.value) || (cfg.defaultCurrency || "INR"));
if (hmCur) {
hmCur.value = state.currency;
hmCur.addEventListener("change", function () {
state.currency = normalizeCurrency(hmCur.value);
});
}
homeRoot.querySelectorAll(".travelgo-gb-trip-opt").forEach(function (btn) {
btn.addEventListener("click", function () {
var type = btn.getAttribute("data-type") || "round_trip";
setTripType(type);
});
});
wireAirportAutocomplete("travelgo-ss-from-input", "travelgo-ss-from-dd", "from");
wireAirportAutocomplete("travelgo-ss-to-input", "travelgo-ss-to-dd", "to");
var swapBtn = $("travelgo-ss-swap");
if (swapBtn)
swapBtn.addEventListener("click", function () {
var tmp = state.from;
state.from = state.to;
state.to = tmp;
updateAirport("from", state.from);
updateAirport("to", state.to);
});
var depDate = $("travelgo-ss-depart-date");
var retDate = $("travelgo-ss-return-date");
if (depDate) depDate.addEventListener("change", updateDateDisplays);
if (retDate) retDate.addEventListener("change", updateDateDisplays);
var retClear = $("travelgo-ss-return-clear");
if (retClear)
retClear.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (retDate) {
retDate.value = "";
state.returnDate = "";
}
});
function fillNumRow(containerId, min, max, current) {
var el = $(containerId);
if (!el) return;
el.innerHTML = "";
var cur = parseInt(current, 10);
for (var i = min; i <= max; i++) {
var b = document.createElement("button");
b.type = "button";
b.className = "travelgo-gb-num-btn" + (i === cur ? " is-active" : "");
b.setAttribute("data-value", String(i));
b.textContent = String(i);
el.appendChild(b);
}
}
function wireNumRow(containerId, field) {
var el = $(containerId);
if (!el) return;
el.addEventListener("click", function (e) {
var t = e.target.closest(".travelgo-gb-num-btn");
if (!t) return;
var v = t.getAttribute("data-value");
popoverDraft[field] = v;
el.querySelectorAll(".travelgo-gb-num-btn").forEach(function (btn) {
btn.classList.toggle("is-active", btn.getAttribute("data-value") === v);
});
});
}
var pop = $("travelgo-gb-popover");
var backdrop = $("travelgo-gb-popover-backdrop");
var paxTrigger = $("travelgo-gb-pax-trigger");
function openPopover() {
popoverDraft.adults = state.adults;
popoverDraft.children = state.children;
popoverDraft.infants = state.infants;
popoverDraft.travelClass = state.travelClass;
popoverDraft.stops = state.stops;
fillNumRow("travelgo-gb-adults-row", 1, 9, popoverDraft.adults);
fillNumRow("travelgo-gb-children-row", 0, 6, popoverDraft.children);
fillNumRow("travelgo-gb-infants-row", 0, 6, popoverDraft.infants);
homeRoot.querySelectorAll(".travelgo-gb-class-btn").forEach(function (b) {
var cl = b.getAttribute("data-class");
b.classList.toggle("is-active", cl === popoverDraft.travelClass);
});
homeRoot.querySelectorAll(".travelgo-gb-stop-btn").forEach(function (b) {
var st = b.getAttribute("data-stops");
b.classList.toggle("is-active", st === popoverDraft.stops);
});
if (pop) {
pop.hidden = false;
}
if (backdrop) {
backdrop.hidden = false;
}
if (paxTrigger) {
paxTrigger.setAttribute("aria-expanded", "true");
}
}
function closePopover() {
if (pop) pop.hidden = true;
if (backdrop) backdrop.hidden = true;
if (paxTrigger) paxTrigger.setAttribute("aria-expanded", "false");
}
wireNumRow("travelgo-gb-adults-row", "adults");
wireNumRow("travelgo-gb-children-row", "children");
wireNumRow("travelgo-gb-infants-row", "infants");
homeRoot.querySelectorAll(".travelgo-gb-class-btn").forEach(function (b) {
b.addEventListener("click", function () {
var cl = b.getAttribute("data-class") || "economy";
popoverDraft.travelClass = cl;
homeRoot.querySelectorAll(".travelgo-gb-class-btn").forEach(function (x) {
x.classList.toggle("is-active", x.getAttribute("data-class") === cl);
});
});
});
homeRoot.querySelectorAll(".travelgo-gb-stop-btn").forEach(function (b) {
b.addEventListener("click", function () {
var st = b.getAttribute("data-stops") || "any";
popoverDraft.stops = st;
homeRoot.querySelectorAll(".travelgo-gb-stop-btn").forEach(function (x) {
x.classList.toggle("is-active", x.getAttribute("data-stops") === st);
});
});
});
if (paxTrigger) {
paxTrigger.addEventListener("click", function (e) {
e.preventDefault();
openPopover();
});
}
if (backdrop) {
backdrop.addEventListener("click", closePopover);
}
var applyBtn = $("travelgo-gb-pax-apply");
if (applyBtn) {
applyBtn.addEventListener("click", function () {
state.adults = popoverDraft.adults;
state.children = popoverDraft.children;
state.infants = popoverDraft.infants;
state.travelClass = popoverDraft.travelClass;
state.stops = popoverDraft.stops;
updatePaxSummary();
closePopover();
});
}
homeRoot.querySelectorAll(".travelgo-gb-fare-opt").forEach(function (b) {
b.addEventListener("click", function () {
var fare = b.getAttribute("data-fare") || "regular";
state.fareType = fare;
homeRoot.querySelectorAll(".travelgo-gb-fare-opt").forEach(function (x) {
x.classList.toggle("is-active", x.getAttribute("data-fare") === fare);
});
});
});
}
function apiSortBy() {
if (state.sortBy === "popularity") return "top_flights";
return state.sortBy;
}
function sortFlightsCopy(flights) {
return flights.slice().sort(function (a, b) {
var stopsA = stopCountFlight(a);
var stopsB = stopCountFlight(b);
if (stopsA !== stopsB) return stopsA - stopsB;
if (state.sortBy === "price") return (a.price || 0) - (b.price || 0);
if (state.sortBy === "duration")
return (a.total_duration || 0) - (b.total_duration || 0);
if (state.sortBy === "popularity") {
var ra = Number(a.rating || 0);
var rb = Number(b.rating || 0);
var ba = Number(a.booking_today || 0);
var bb = Number(b.booking_today || 0);
if (!isFinite(ra)) ra = 0;
if (!isFinite(rb)) rb = 0;
var sa = ra * 20 + Math.min(35, ba / 2);
var sb = rb * 20 + Math.min(35, bb / 2);
if (Math.abs(sb - sa) > 0.01) return sb - sa;
return (a.price || 0) - (b.price || 0);
}
if (state.sortBy === "top_flights") {
var pa =
a._travelgo_policy && typeof a._travelgo_policy.score === "number"
? a._travelgo_policy.score
: 0;
var pb =
b._travelgo_policy && typeof b._travelgo_policy.score === "number"
? b._travelgo_policy.score
: 0;
if (Math.abs(pb - pa) > 0.01) return pb - pa;
return (a.price || 0) - (b.price || 0);
}
if (state.sortBy === "departure_time") {
var ta =
a.flights && a.flights[0] && a.flights[0].departure_airport
? a.flights[0].departure_airport.time || ""
: "";
var tb =
b.flights && b.flights[0] && b.flights[0].departure_airport
? b.flights[0].departure_airport.time || ""
: "";
return ta.localeCompare(tb);
}
return 0;
});
}
function bindSortContainer(container) {
if (!container) return;
container.addEventListener("click", function (e) {
var pill = e.target.closest(".travelgo-ss-sort-pill");
if (!pill || !container.contains(pill)) return;
container.querySelectorAll(".travelgo-ss-sort-pill").forEach(function (p) {
p.classList.remove("is-active");
});
pill.classList.add("is-active");
state.sortBy = pill.getAttribute("data-sort") || "top_flights";
syncSortUi();
if (!state.allFlights.length) return;
if (isResults) applyFiltersAndRender();
});
}
if (isResults && resultsRoot) {
bindSortContainer(resultsRoot.querySelector("#travelgo-ss-sort-pills"));
}
function stopCountFlight(f) {
if (
typeof f.number_of_stops === "number" &&
!isNaN(f.number_of_stops) &&
f.number_of_stops >= 0
) {
return f.number_of_stops;
}
return (f.flights && f.flights.length ? f.flights.length : 1) - 1;
}
function departureHour(f) {
var t =
f.flights &&
f.flights[0] &&
f.flights[0].departure_airport &&
f.flights[0].departure_airport.time;
if (!t) return null;
var parts = String(t).split(":");
var h = parseInt(parts[0], 10);
return isNaN(h) ? null : h;
}
function anyStopFilterOn() {
return (
filterState.stopsNonstop ||
filterState.stopsOne ||
filterState.stopsTwoPlus
);
}
function anyDepFilterOn() {
return (
filterState.depBefore6 ||
filterState.dep6to12 ||
filterState.dep12to18 ||
filterState.depAfter18
);
}
function matchesStopFilters(f) {
if (!anyStopFilterOn()) return true;
var sc = stopCountFlight(f);
if (filterState.stopsNonstop && sc === 0) return true;
if (filterState.stopsOne && sc === 1) return true;
if (filterState.stopsTwoPlus && sc >= 2) return true;
return false;
}
function matchesDepFilters(f) {
if (!anyDepFilterOn()) return true;
var h = departureHour(f);
if (h === null) return true;
if (filterState.depBefore6 && h < 6) return true;
if (filterState.dep6to12 && h >= 6 && h < 12) return true;
if (filterState.dep12to18 && h >= 12 && h < 18) return true;
if (filterState.depAfter18 && h >= 18) return true;
return false;
}
function matchesPriceFilter(f) {
if (filterState.priceMaxUser == null) return true;
var p = f.price || 0;
return p <= filterState.priceMaxUser;
}
function matchesAirlineFilter(f) {
if (!filterState.airlinesAllowed) return true;
var name =
(f.flights && f.flights[0] && f.flights[0].airline) || "";
return filterState.airlinesAllowed.indexOf(name) !== -1;
}
function flightMatchesFilters(f) {
return (
matchesStopFilters(f) &&
matchesDepFilters(f) &&
matchesPriceFilter(f) &&
matchesAirlineFilter(f)
);
}
function getFilteredFlights() {
if (!state.allFlights.length) return [];
return state.allFlights.filter(flightMatchesFilters);
}
function minPriceWhere(pred) {
var m = Infinity;
state.allFlights.forEach(function (f) {
if (pred(f)) m = Math.min(m, f.price || 0);
});
return m === Infinity ? null : m;
}
function updateFilterHints() {
var fp = S.fromPrice || "from";
var h0 = $("travelgo-fr-hint-nonstop");
var h1 = $("travelgo-fr-hint-1stop");
var h2 = $("travelgo-fr-hint-2plus");
var mn0 = minPriceWhere(function (f) {
return stopCountFlight(f) === 0;
});
var mn1 = minPriceWhere(function (f) {
return stopCountFlight(f) === 1;
});
var mn2 = minPriceWhere(function (f) {
return stopCountFlight(f) >= 2;
});
function setHint(el, val) {
if (!el) return;
el.textContent =
val != null
? fp + " " + formatMoney(val)
: "";
}
setHint(h0, mn0);
setHint(h1, mn1);
setHint(h2, mn2);
}
function syncPriceSliderFromData() {
var el = $("travelgo-fr-price-slider");
var minL = $("travelgo-fr-price-min-lbl");
var maxL = $("travelgo-fr-price-max-lbl");
var cur = $("travelgo-fr-price-current");
if (!el || !state.allFlights.length) return;
var prices = state.allFlights.map(function (f) {
return f.price || 0;
});
var mn = Math.min.apply(null, prices);
var mx = Math.max.apply(null, prices);
el.min = String(Math.max(0, mn - (mn % 100)));
el.max = String(mx);
el.step = mx - mn > 5000 ? "500" : "100";
filterState.priceMaxUser = mx;
el.value = String(mx);
if (minL) minL.textContent = formatMoney(mn);
if (maxL) maxL.textContent = formatMoney(mx);
if (cur) {
cur.textContent =
(S.maxPriceLabel || "Showing up to") +
" " +
formatMoney(mx);
}
}
function syncAirlineFilterFromDom() {
var host = $("travelgo-fr-airlines");
if (!host) return;
var cbs = host.querySelectorAll(".travelgo-fr-al-cb");
var checked = [];
var n = 0;
cbs.forEach(function (cb) {
n++;
if (cb.checked) checked.push(cb.getAttribute("data-airline") || "");
});
filterState.airlinesAllowed = checked.length === n ? null : checked;
}
var OBSOLETE_AIRLINE_RE =
/air\s*sahara|air\s*pegasus|go\s*air\b|eurocypria|astral\s*aviation|kingfisher|jet\s*airways|go\s*first|air\s*deccan|modi\s*luft/i;
function rebuildAirlineFilters(flights) {
var host = $("travelgo-fr-airlines");
if (!host) return;
var names = {};
flights.forEach(function (f) {
var n = (f.flights && f.flights[0] && f.flights[0].airline) || "";
if (n && !OBSOLETE_AIRLINE_RE.test(String(n))) names[n] = true;
});
var list = Object.keys(names).sort();
if (!list.length) {
host.innerHTML =
'' +
escapeHtml(S.noAirlines || "—") +
"
";
return;
}
host.innerHTML = list
.map(function (name, i) {
var sid = "travelgo-fr-al-" + i;
var a = String(name)
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/' +
' ' +
'' +
escapeHtml(name) +
" "
);
})
.join("");
filterState.airlinesAllowed = null;
}
function clearAllFiltersUi() {
filterState.stopsNonstop =
filterState.stopsOne =
filterState.stopsTwoPlus =
false;
filterState.depBefore6 =
filterState.dep6to12 =
filterState.dep12to18 =
filterState.depAfter18 =
false;
var n1 = $("travelgo-fr-f-nonstop");
var n2 = $("travelgo-fr-f-1stop");
var n3 = $("travelgo-fr-f-2plus");
if (n1) n1.checked = false;
if (n2) n2.checked = false;
if (n3) n3.checked = false;
ROOT.querySelectorAll(".travelgo-fr-time-tile").forEach(function (b) {
b.classList.remove("is-selected");
});
var host = $("travelgo-fr-airlines");
if (host) {
host.querySelectorAll(".travelgo-fr-al-cb").forEach(function (cb) {
cb.checked = true;
});
}
filterState.airlinesAllowed = null;
syncPriceSliderFromData();
}
function applyFiltersAndRender() {
var filtered = getFilteredFlights();
state.currentResults = filtered;
var countEl = res$("results-count");
if (countEl && state.allFlights.length) {
countEl.textContent =
filtered.length +
" " +
(filtered.length === 1
? S.flightSingular || "flight"
: S.flightPlural || "flights") +
" " +
(S.found || "found");
}
renderFlights(filtered, state.currentMeta);
}
function wireResultsFilters() {
if (!isResults || filtersWired) return;
var sidebar = $("travelgo-fr-sidebar");
if (!sidebar) return;
filtersWired = true;
sidebar.addEventListener("change", function (ev) {
filterState.stopsNonstop = !!(
$("travelgo-fr-f-nonstop") && $("travelgo-fr-f-nonstop").checked
);
filterState.stopsOne = !!(
$("travelgo-fr-f-1stop") && $("travelgo-fr-f-1stop").checked
);
filterState.stopsTwoPlus = !!(
$("travelgo-fr-f-2plus") && $("travelgo-fr-f-2plus").checked
);
var t = ev.target;
if (t && t.classList && t.classList.contains("travelgo-fr-al-cb")) {
syncAirlineFilterFromDom();
}
if (state.allFlights.length) applyFiltersAndRender();
});
var slider = $("travelgo-fr-price-slider");
if (slider) {
slider.addEventListener("input", function () {
filterState.priceMaxUser = parseInt(slider.value, 10) || 0;
var cur = $("travelgo-fr-price-current");
if (cur) {
cur.textContent =
(S.maxPriceLabel || "Showing up to") +
" " +
formatMoney(filterState.priceMaxUser);
}
if (state.allFlights.length) applyFiltersAndRender();
});
}
sidebar.addEventListener("click", function (e) {
var btn = e.target.closest(".travelgo-fr-time-tile");
if (!btn || !sidebar.contains(btn)) return;
var dep = btn.getAttribute("data-fr-dep");
if (!dep) return;
var map = {
before6: "depBefore6",
m6_12: "dep6to12",
m12_18: "dep12to18",
after18: "depAfter18",
};
var key = map[dep];
if (!key) return;
filterState[key] = !filterState[key];
btn.classList.toggle("is-selected", filterState[key]);
if (state.allFlights.length) applyFiltersAndRender();
});
var clr = $("travelgo-fr-clear-filters");
if (clr) {
clr.addEventListener("click", function () {
clearAllFiltersUi();
if (state.allFlights.length) applyFiltersAndRender();
});
}
}
function renderSkeletons() {
return Array(4)
.fill("")
.map(function () {
return (
'"
);
})
.join("");
}
/** City / area label under times (from airport name or IATA). */
function airportCityFromAp(ap) {
if (!ap || typeof ap !== "object") return "";
var name = ap.name ? String(ap.name) : "";
if (name) {
var s = name
.replace(/\s+International Airport/gi, "")
.replace(/\s+Airport/gi, "")
.trim();
var cut = s.split(",")[0].trim();
if (cut) return cut;
}
return ap.id ? String(ap.id) : "";
}
/** Official airline logos (Kiwi CDN) first; theme bundle only as fallback. */
function extractIataFromFlightNum(seg) {
var raw = String(seg && seg.flight_number ? seg.flight_number : "").trim();
if (!raw) return "";
var compact = raw.toUpperCase().replace(/[\s\-_]/g, "");
if (compact.length >= 3 && /^[A-Z0-9]{2}\d/.test(compact)) {
return compact.slice(0, 2);
}
var m = raw.toUpperCase().match(/^([A-Z0-9]{2})\s*\d/);
return m ? m[1] : "";
}
function icaoFromSegment(seg) {
if (!seg || typeof seg !== "object") return "";
var iata = extractIataFromFlightNum(seg);
var nm = String(seg.airline || "").toLowerCase();
if (iata === "6E" || nm.indexOf("indigo") !== -1) return "IGO";
if (iata === "AI" || (nm.indexOf("air india") !== -1 && nm.indexOf("express") === -1)) return "AIC";
if (iata === "IX" || nm.indexOf("air india express") !== -1) return "AXB";
if (iata === "UK" || nm.indexOf("vistara") !== -1) return "VTI";
if (iata === "SG" || nm.indexOf("spicejet") !== -1) return "SEJ";
if (iata === "QP" || nm.indexOf("akasa") !== -1) return "AKJ";
if (iata === "G8" || nm.indexOf("go air") !== -1 || nm.indexOf("goair") !== -1) return "GOW";
return "";
}
function hasBundledAirlineLogo(icao) {
if (!icao) return false;
return BUNDLED_AIRLINE_ICAO.indexOf(icao) !== -1;
}
function bundledAirlineLogoUrl(icao) {
if (!AIRLINE_LOGOS_URI || !icao || !hasBundledAirlineLogo(icao)) return "";
var q = ASSET_VERSION ? "?v=" + encodeURIComponent(ASSET_VERSION) : "";
return AIRLINE_LOGOS_URI + "/" + icao + ".svg" + q;
}
function remoteAirlineLogoUrl(iata) {
if (!iata || iata.length !== 2) return "";
if (!REMOTE_AIRLINE_LOGO_URL || REMOTE_AIRLINE_LOGO_URL.indexOf("%s") === -1) return "";
return REMOTE_AIRLINE_LOGO_URL.replace("%s", encodeURIComponent(iata));
}
function resolveAirlineLogoPair(seg) {
var apiLogo = String((seg && seg.airline_logo) || "").trim();
var iata = extractIataFromFlightNum(seg);
var icao = icaoFromSegment(seg);
var bundled = bundledAirlineLogoUrl(icao);
var remote = remoteAirlineLogoUrl(iata);
var primary = remote || apiLogo || bundled;
var fallback = "";
if (primary === remote) {
fallback = apiLogo && apiLogo !== remote ? apiLogo : bundled;
} else if (primary === apiLogo) {
fallback = remote && remote !== apiLogo ? remote : bundled;
} else if (primary === bundled) {
fallback = remote || apiLogo;
}
if (fallback && fallback === primary) fallback = "";
return { primary: primary, fallback: fallback };
}
function airlineLogoImgHtml(seg, airlineName) {
var logos = resolveAirlineLogoPair(seg);
if (!logos.primary) return "✈";
var isBundledWide =
logos.primary.indexOf("kiwi.com") === -1 &&
logos.primary.indexOf("searchapi") === -1 &&
(logos.primary.indexOf("/AIC.") !== -1 || logos.primary.indexOf("/AXB.") !== -1);
var wideClass = isBundledWide ? " travelgo-ss-airline-logo-img--wide" : "";
var fb = logos.fallback ? logos.fallback.replace(/"/g, """) : "";
return (
' '
);
}
function resolveBundledAirlineLogo(seg) {
return bundledAirlineLogoUrl(icaoFromSegment(seg));
}
function buildFlightCard(f, idx) {
var firstFlight = f.flights && f.flights[0] ? f.flights[0] : {};
var lastIdx = f.flights && f.flights.length ? f.flights.length - 1 : 0;
var lastFlight = f.flights && f.flights[lastIdx] ? f.flights[lastIdx] : {};
var depAirport = firstFlight.departure_airport || {};
var arrAirport = lastFlight.arrival_airport || {};
var policyNotes =
f._travelgo_policy && Array.isArray(f._travelgo_policy.notes)
? f._travelgo_policy.notes
: [];
var mins = f.total_duration || 0;
var durStr = Math.floor(mins / 60) + " h " + (mins % 60) + " m";
var stopCount =
typeof f.number_of_stops === "number" &&
!isNaN(f.number_of_stops) &&
f.number_of_stops >= 0
? f.number_of_stops
: (f.flights && f.flights.length ? f.flights.length : 1) - 1;
var viaLabel = "";
if (stopCount > 0) {
if (f.layovers && f.layovers[0]) {
viaLabel = String(f.layovers[0].name || f.layovers[0].id || "");
viaLabel = viaLabel.replace(/\s+International Airport/gi, "").split(",")[0].trim();
} else if (f.flights && f.flights.length > 1) {
viaLabel = airportCityFromAp(f.flights[0].arrival_airport || {});
}
}
var stopsLabelRaw =
stopCount === 0
? S.nonstopLabel || "Non stop"
: stopCount === 1
? (S.stopViaOne || "1 stop via") + " " + (viaLabel || S.viaUnknown || "connection")
: stopCount + " " + (S.stopsMany || "stops");
var stopsClass = stopCount === 0 ? "travelgo-ss-nonstop" : "";
var price = f.price || 0;
var pricing = f._travelgoPricing || null;
var isOvernightAny =
f.flights && f.flights.some(function (x) {
return x.is_overnight;
});
var isDelayedAny =
f.flights &&
f.flights.some(function (x) {
return x.is_often_delayed;
});
var airlineName = firstFlight.airline || "";
var flightNum = firstFlight.flight_number || "";
var plane = firstFlight.airplane || "";
var allFlightNums = (f.flights || [])
.map(function (s) {
return s && s.flight_number ? String(s.flight_number).trim() : "";
})
.filter(Boolean)
.join(", ");
if (!allFlightNums) {
allFlightNums = flightNum;
}
var depTime = formatClockTime(depAirport.time);
var arrTime = formatClockTime(arrAirport.time);
var depCode = depAirport.id || state.from;
var arrCode = arrAirport.id || state.to;
var depCity = airportCityFromAp(depAirport);
var arrCity = airportCityFromAp(arrAirport);
var priceNotes = [];
if (pricing && pricing.convenienceFee > 0) {
priceNotes.push(
"Incl. conv. fee " + formatMoney(pricing.convenienceFee)
);
}
if (pricing && pricing.couponAmount > 0) {
priceNotes.push("Coupon up to " + formatMoney(pricing.couponAmount));
}
var segRows = "";
if (f.flights && f.flights.length) {
var meta = state.currentMeta && state.currentMeta._travelgo_search;
var outFromMeta = meta && meta.outbound_date ? String(meta.outbound_date) : "";
var retFromMeta = meta && meta.return_date ? String(meta.return_date) : "";
var implicitDate =
normalizeSearchDateToIso(state.departDate || "") ||
normalizeSearchDateToIso(outFromMeta) ||
"";
var normalizedReturn =
normalizeSearchDateToIso(state.returnDate || "") ||
normalizeSearchDateToIso(retFromMeta) ||
"";
var retLegStart = -1;
if (state.tripType === "round_trip" && normalizedReturn && state.from && state.to) {
for (var rj = 0; rj < f.flights.length; rj++) {
var sf = f.flights[rj];
var did =
sf.departure_airport && sf.departure_airport.id
? String(sf.departure_airport.id).toUpperCase()
: "";
if (did === String(state.to).toUpperCase()) {
retLegStart = rj;
break;
}
}
}
segRows = f.flights
.map(function (seg, si) {
if (retLegStart >= 0 && si === retLegStart && normalizedReturn) {
implicitDate = normalizedReturn;
}
var depAp = seg.departure_airport || {};
var arrAp = seg.arrival_airport || {};
var depParsed = parseAirportDateTime(depAp.time);
var depLabel =
depParsed.iso !== ""
? formatDateWithTime(depParsed.iso, depParsed.clock)
: formatDateWithTime(implicitDate, depAp.time);
var depDayIso = normalizeSearchDateToIso(
depParsed.iso !== "" ? depParsed.iso : implicitDate
);
var arrParsed = parseAirportDateTime(arrAp.time);
var arrDayIso;
if (arrParsed.iso !== "") {
arrDayIso = normalizeSearchDateToIso(arrParsed.iso);
} else if (depDayIso) {
arrDayIso = depDayIso;
if (seg.is_overnight) {
arrDayIso = addDaysIso(depDayIso, 1);
} else {
var dm = clockToMins(depAp.time);
var am = clockToMins(arrAp.time);
if (dm !== null && am !== null && am < dm) {
arrDayIso = addDaysIso(depDayIso, 1);
}
}
} else {
arrDayIso = "";
}
var arrLabel =
arrParsed.iso !== ""
? formatDateWithTime(arrParsed.iso, arrParsed.clock)
: formatDateWithTime(arrDayIso, arrAp.time);
implicitDate = normalizeSearchDateToIso(
arrParsed.iso !== "" ? arrParsed.iso : arrDayIso
) || implicitDate;
return (
'' +
'
✈
' +
'
' +
(depAp.id ? String(depAp.id) : "") +
" " +
depLabel +
" → " +
(arrAp.id ? String(arrAp.id) : "") +
" " +
arrLabel +
" " +
(seg.airline || "") +
" " +
(seg.flight_number || "") +
" · " +
Math.floor((seg.duration || 0) / 60) +
"h " +
((seg.duration || 0) % 60) +
"m
"
);
})
.join("");
}
var layoverRows =
f.layovers && f.layovers.length
? f.layovers
.map(function (lay) {
return (
'' +
"⏱ " +
Math.floor(lay.duration / 60) +
"h " +
(lay.duration % 60) +
"m " +
(S.layoverAt || "layover at") +
" " +
(lay.name || "") +
" (" +
(lay.id || "") +
")" +
"
"
);
})
.join("")
: "";
var policyHtml =
policyNotes.length > 0
? '' +
policyNotes
.map(function (n) {
return (
'' +
escapeHtml(String(n)) +
" "
);
})
.join("") +
"
"
: "";
var ribL = "";
if (
policyNotes.some(function (n) {
return /meal|dining|food|breakfast/i.test(String(n));
})
) {
ribL = S.freeMealRibbon || "Meal included on select fares";
} else if (/air india(?!\s+express)/i.test(airlineName)) {
ribL = S.freeMealRibbon || "Meal on long routes · confirm with airline";
}
var ribR = !isDelayedAny
? String(80 + ((idx * 5) % 19)) + "% " + (S.onTimeRibbon || "on time")
: S.delaysPossible || "Allow time for connections";
var ribbonHtml =
ribL || ribR
? '' +
(ribL
? '' +
escapeHtml(ribL) +
" "
: "") +
(ribR
? '' +
escapeHtml(ribR) +
" "
: "") +
"
"
: "";
var overnightTag = isOvernightAny
? '' + escapeHtml(S.plusOneDay || "+1 DAY") + " "
: "";
var lockFee = Math.max(49, Math.round(Number(price) * 0.033));
var lockLine = String(S.lockPriceFor || "Lock this price for %s").replace(
"%s",
formatMoney(lockFee)
);
var promoText =
S.promoStripDefault ||
"Bank & UPI offers at checkout · fare shown is indicative — confirm on booking page.";
var metaHiddenBits = [];
if (plane) metaHiddenBits.push(escapeHtml(plane));
if (isDelayedAny) metaHiddenBits.push(escapeHtml(S.oftenDelayed || "Often delayed"));
var metaHidden =
metaHiddenBits.length > 0
? '' + metaHiddenBits.join(" · ") + "
"
: "";
return (
'' +
ribbonHtml +
'
' +
'
' +
'
' +
airlineLogoImgHtml(firstFlight, airlineName) +
"
" +
'
' +
'
' +
escapeHtml(airlineName) +
"
" +
'
' +
escapeHtml(allFlightNums) +
'
' +
escapeHtml(S.comparePlus || "COMPARE +") +
" " +
'
' +
'
' +
escapeHtml(depTime) +
'
' +
escapeHtml(depCity || depCode) +
'
' +
escapeHtml(depCode) +
"
" +
'
' +
'
' +
escapeHtml(durStr) +
'
' +
'
' +
escapeHtml(stopsLabelRaw) +
"
" +
'
' +
'
' +
escapeHtml(arrTime) +
overnightTag +
'
' +
escapeHtml(arrCity || arrCode) +
'
' +
escapeHtml(arrCode) +
"
" +
'
' +
'
' +
currencySymbol(state.currency) +
" " +
Number(price).toLocaleString(currencyLocale(state.currency)) +
"
" +
'
' +
escapeHtml(S.perAdult || S.perPerson || "per adult") +
"
" +
'
' +
escapeHtml(priceNotes.join(" · ")) +
"
" +
'
' +
escapeHtml(S.viewFares || S.bookNow || "VIEW FARES") +
" " +
'
' +
escapeHtml(String(lockLine)) +
" " +
layoverRows +
'" +
'
' +
metaHidden +
policyHtml +
segRows +
"
"
);
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&")
.replace(/🔍
' +
(S.noFlightsTitle || "No flights found") +
" " +
(S.noFilterMatch || "No flights match your filters. Try clearing filters.") +
"
";
return;
}
list.innerHTML = sorted.map(function (f, i) {
return buildFlightCard(f, i);
}).join("");
state.lastRenderedFlights = sorted;
list.querySelectorAll(".travelgo-ss-expand").forEach(function (t) {
t.addEventListener("click", function () {
var card = t.closest(".travelgo-ss-flight-card");
var seg = card ? card.querySelector(".travelgo-ss-segments") : null;
var tx = t.querySelector(".travelgo-ss-expand-text");
if (!card || !seg) return;
seg.hidden = !seg.hidden;
t.setAttribute("aria-expanded", seg.hidden ? "false" : "true");
if (tx) {
var openLab = S.viewFlightDetails || S.viewDetails || "View details";
tx.textContent = seg.hidden ? openLab : S.hideDetails || "Hide details";
}
});
});
list.querySelectorAll(".travelgo-ss-compare-btn").forEach(function (b) {
b.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
});
});
list.querySelectorAll(".travelgo-ss-lock-btn").forEach(function (b) {
b.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
});
});
list.querySelectorAll(".travelgo-ss-book-btn").forEach(function (b) {
b.addEventListener("click", function (e) {
e.stopPropagation();
if (!BOOKING_ENABLED || !AJAX_URL || !BOOKING_NONCE) {
window.alert(S.bookingNotConfigured || S.bookingSaveError || "Booking unavailable.");
return;
}
var card = b.closest(".travelgo-ss-flight-card");
var rawIdx = card ? card.getAttribute("data-travelgo-flight-index") : null;
var idx = rawIdx != null ? parseInt(rawIdx, 10) : NaN;
var flight =
!isNaN(idx) && state.lastRenderedFlights
? state.lastRenderedFlights[idx]
: null;
if (!flight) {
window.alert(S.bookingSaveError || "Could not read this flight.");
return;
}
var fd = new FormData();
fd.append("action", "travelgo_save_flight_booking");
fd.append("nonce", BOOKING_NONCE);
fd.append("payload", JSON.stringify(buildBookingPayload(flight)));
b.disabled = true;
fetch(AJAX_URL, { method: "POST", body: fd, credentials: "same-origin" })
.then(function (r) {
return r.json();
})
.then(function (res) {
b.disabled = false;
if (!res || !res.success || !res.data || !res.data.redirect) {
var msg =
(res && res.data && res.data.message) ||
S.bookingSaveError ||
"Error";
window.alert(msg);
return;
}
window.location.href = res.data.redirect;
})
.catch(function () {
b.disabled = false;
window.alert(S.bookingSaveError || "Error");
});
});
});
}
function refreshNearbyHotelsStrip() {
if (!isResults || !resultsRoot) return;
var root = resultsRoot.querySelector("#travelgo-fr-nearby-root");
var hint = resultsRoot.querySelector("#travelgo-fr-nearby-hint");
var grid = resultsRoot.querySelector("#travelgo-fr-nearby-grid");
var chk = resultsRoot.querySelector("#travelgo-fr-stay-airport");
if (!root || !grid) return;
if (!HAS_GOOGLE_MAPS || !MAPS_NEARBY_URL) {
root.hidden = true;
return;
}
if (!state.to || state.to.length !== 3) {
root.hidden = true;
return;
}
root.hidden = false;
if (hint) {
hint.textContent = S.loadingHotelsNearAirport || "Loading hotels near airport…";
}
grid.innerHTML = "";
if (chk && !chk.dataset.travelgoWired) {
chk.dataset.travelgoWired = "1";
chk.addEventListener("change", function () {
refreshNearbyHotelsStrip();
});
}
fetch(MAPS_NEARBY_URL, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "same-origin",
body: JSON.stringify({
airport_iata: state.to,
stay_near_airport: !!(chk && chk.checked),
}),
})
.then(function (r) {
return r.json().then(function (data) {
return { ok: r.ok, data: data };
});
})
.then(function (wrapped) {
var data = wrapped && wrapped.data;
if (!wrapped || !wrapped.ok || !data || !Array.isArray(data.places)) {
if (hint) {
hint.textContent =
(data && data.message) ||
S.hotelsNearAirportError ||
"Could not load nearby hotels.";
}
return;
}
if (hint) hint.textContent = "";
if (!data.places.length) {
if (hint) hint.textContent = S.noHotelsNearAirport || "No lodging results.";
return;
}
grid.innerHTML = data.places
.map(function (p) {
var dollars =
typeof p.price_level === "number" && p.price_level >= 0
? " · " + "$$$$$".slice(0, Math.min(4, p.price_level + 1))
: "";
var rate =
p.rating && isFinite(Number(p.rating))
? "\u2B50 " + Number(p.rating).toFixed(1)
: "";
var dist =
p.distance_km != null && isFinite(Number(p.distance_km))
? " · " + p.distance_km + " km"
: "";
var href = String(p.maps_url || "#").replace(/"/g, """);
return (
'' +
escapeHtml(p.name || "") +
' ' +
escapeHtml(rate + dollars + dist) +
'
' +
escapeHtml(S.openInMaps || "Open in Google Maps") +
" "
);
})
.join("");
})
.catch(function () {
if (hint) {
hint.textContent =
S.hotelsNearAirportError ||
"Could not load nearby hotels.";
}
});
}
function handleResults(data) {
var ts = data && data._travelgo_search;
if (ts && typeof ts === "object") {
if (ts.outbound_date) {
var oIso = normalizeSearchDateToIso(String(ts.outbound_date));
if (oIso) state.departDate = oIso;
}
if (ts.return_date) {
var rIso = normalizeSearchDateToIso(String(ts.return_date));
if (rIso) state.returnDate = rIso;
}
}
var best = data.best_flights || [];
var other = data.other_flights || [];
var flights = best.concat(other);
flights.forEach(function (f) {
var hasServerPricing =
f &&
typeof f === "object" &&
f._travelgo_pricing &&
typeof f._travelgo_pricing === "object" &&
isFinite(Number(f._travelgo_base_price || 0));
if (hasServerPricing) {
var pp = f._travelgo_pricing;
f._travelgo_base_price = Number(f._travelgo_base_price || 0);
f._travelgoPricing = {
baseFare: Number(pp.base_fare || f._travelgo_base_price || 0),
markupPercent: Number(pp.markup_percent || 0),
markupAmount: Number(pp.markup_amount || 0),
convenienceFee: Number(pp.convenience_fee || 0),
grossTotal: Number(pp.gross_total || f.price || 0),
couponType: String(pp.coupon_type || "none"),
couponValue: Number(pp.coupon_value || 0),
couponCode: String(pp.coupon_code || ""),
couponAmount: Number(pp.coupon_amount || 0),
cancelType: String(pp.cancel_type || "none"),
cancelValue: Number(pp.cancel_value || 0),
cancelAmount: Number(pp.cancel_amount || 0),
estimatedRefund: Number(pp.estimated_refund || 0),
refundNote: String(pp.refund_note || ""),
};
f.price = Math.round(Number(f.price || f._travelgoPricing.grossTotal || 0));
} else {
var original = Number(f.price || 0);
if (!isFinite(original)) original = 0;
f._travelgo_base_price = original;
var p = applyPricingToFare(original);
f._travelgoPricing = p;
f.price = Math.round(Number(p.grossTotal || 0));
}
});
flights = flights.filter(function (f) {
var display = Number(f.price || 0);
var base = Number(f._travelgo_base_price || 0);
return isFinite(display) && display > 1 && isFinite(base) && base > 0;
});
state.allFlights = flights;
state.currentResults = flights;
state.currentMeta = data;
var piBlock = res$("price-insight");
if (piBlock && data.price_insights) {
var pi = data.price_insights;
var allAdjustedPrices = flights.map(function (f) { return Number(f.price || 0); }).filter(function (p) { return isFinite(p) && p > 0; }).sort(function (a, b) { return a - b; });
var low = allAdjustedPrices.length ? allAdjustedPrices[0] : (pi.lowest_price || 0);
var rangeLow = allAdjustedPrices.length ? allAdjustedPrices[Math.floor((allAdjustedPrices.length - 1) * 0.25)] : ((pi.typical_price_range && pi.typical_price_range.low_price) || 0);
var rangeHigh = allAdjustedPrices.length ? allAdjustedPrices[Math.floor((allAdjustedPrices.length - 1) * 0.75)] : ((pi.typical_price_range && pi.typical_price_range.high_price) || 0);
var levelMap = {
low: { label: S.priceLow || "Low price", cls: "travelgo-ss-pi-low" },
typical: { label: S.priceTypical || "Typical price", cls: "" },
high: { label: S.priceHigh || "High price", cls: "travelgo-ss-pi-high" },
};
var lv = levelMap[pi.price_level] || levelMap.typical;
piBlock.innerHTML =
'' +
'
💡
' +
'
' +
(S.lowestFound || "Lowest found") +
": " +
formatMoney(low) +
" " +
(S.typicalRange || "Typical range") +
" " +
formatMoney(rangeLow) +
" – " +
formatMoney(rangeHigh) +
"
" +
'
' +
lv.label +
"
";
} else if (piBlock) piBlock.innerHTML = "";
var list = res$("flight-list");
var countEl = res$("results-count");
if (!flights.length) {
state.allFlights = [];
if (list)
list.innerHTML =
'' +
'
✈️
' +
(S.noFlightsTitle || "No flights found") +
" " +
(S.noFlightsHint || "Try adjusting your dates or airports.") +
"
";
if (countEl) countEl.textContent = S.zeroFlights || "0 flights found";
if (isResults && resultsRoot) {
var nr = resultsRoot.querySelector("#travelgo-fr-nearby-root");
if (nr) nr.hidden = true;
}
return;
}
if (isResults) {
rebuildAirlineFilters(flights);
syncPriceSliderFromData();
updateFilterHints();
applyFiltersAndRender();
refreshNearbyHotelsStrip();
}
}
function buildTfQuery() {
var p = new URLSearchParams();
p.set("tf_from", state.from);
p.set("tf_to", state.to);
p.set("tf_dep", state.departDate);
p.set("tf_trip", state.tripType);
if (state.tripType === "round_trip" && state.returnDate) p.set("tf_ret", state.returnDate);
p.set("tf_adults", state.adults);
p.set("tf_ch", state.children);
p.set("tf_inf", state.infants);
p.set("tf_class", state.travelClass);
p.set("tf_stops", state.stops);
p.set("tf_sort", state.sortBy);
p.set("tf_fare", state.fareType);
p.set("tf_cur", state.currency);
return p.toString();
}
function normCode(s, fallback) {
var c = String(s || "")
.toUpperCase()
.replace(/[^A-Z]/g, "")
.substring(0, 3);
return c.length === 3 ? c : fallback;
}
function parseTfNum(p, key, defVal, min, max) {
var v = p.get(key);
if (v == null || v === "") return String(defVal);
var n = parseInt(v, 10);
if (isNaN(n)) return String(defVal);
n = Math.max(min, Math.min(max, n));
return String(n);
}
function syncSortUi() {
var sp = resultsRoot
? resultsRoot.querySelector("#travelgo-ss-sort-pills")
: null;
if (!sp) return;
sp.querySelectorAll(".travelgo-ss-sort-pill").forEach(function (pill) {
var d = pill.getAttribute("data-sort") || "";
pill.classList.toggle("is-active", d === state.sortBy);
});
}
function initResultsFromUrl() {
var p = new URLSearchParams(window.location.search);
state.from = normCode(p.get("tf_from"), "DEL");
state.to = normCode(p.get("tf_to"), "BOM");
var trip = p.get("tf_trip") || "one_way";
if (trip !== "one_way" && trip !== "round_trip") {
trip = "one_way";
}
state.tripType = trip;
state.departDate =
normalizeSearchDateToIso(p.get("tf_dep") || "") || String(p.get("tf_dep") || "").trim();
state.returnDate =
normalizeSearchDateToIso(p.get("tf_ret") || "") || String(p.get("tf_ret") || "").trim();
state.adults = parseTfNum(p, "tf_adults", 1, 1, 9);
state.children = parseTfNum(p, "tf_ch", 0, 0, 6);
state.infants = parseTfNum(p, "tf_inf", 0, 0, 6);
var tc = p.get("tf_class") || "economy";
var okc = {
economy: 1,
premium_economy: 1,
business: 1,
first_class: 1,
};
state.travelClass = okc[tc] ? tc : "economy";
var st = p.get("tf_stops") || "any";
var oks = { any: 1, nonstop: 1, one_stop_or_fewer: 1 };
state.stops = oks[st] ? st : "any";
var rawSort = p.get("tf_sort") || "top_flights";
var sortOk = {
top_flights: 1,
price: 1,
duration: 1,
departure_time: 1,
popularity: 1,
};
state.sortBy = sortOk[rawSort] ? rawSort : "top_flights";
state.fareType = p.get("tf_fare") || "regular";
state.currency = normalizeCurrency(p.get("tf_cur") || (cfg.defaultCurrency || "INR"));
var rsCur = document.getElementById("travelgo-fr-currency-select");
if (rsCur) rsCur.value = state.currency;
var headline = $("travelgo-fr-headline");
if (headline) {
headline.textContent =
(S.flightsFromPrefix || "Flights from ") +
cityLine(state.from) +
(S.flightsFromToMid || " to ") +
cityLine(state.to) +
(state.tripType === "round_trip"
? S.flightsAndBack || ", and back"
: "");
}
var routeTitle = $("travelgo-ss-route-title");
if (routeTitle) {
routeTitle.textContent = state.from + " → " + state.to;
}
syncSortUi();
ensureResultsLoadingOverlay();
renderFareCalendar();
fetchFlightData();
}
function ensureResultsLoadingOverlay() {
if (!resultsRoot || res$("loading-overlay")) return;
var host = document.createElement("div");
host.id = "travelgo-ss-loading-overlay";
host.className = "travelgo-fr-loading-overlay";
host.hidden = true;
host.innerHTML =
'' +
' ' +
'' +
escapeHtml(S.fetching || "Fetching results…") +
"
";
resultsRoot.appendChild(host);
}
async function fetchFlightData() {
if (state.tripType === "multi_city") return;
if (!state.from || !state.to || !state.departDate) return;
if (state.tripType === "round_trip" && !state.returnDate) return;
var list = res$("flight-list");
var loading = res$("loading-overlay");
if (loading) loading.hidden = false;
var countEl = res$("results-count");
if (list) list.innerHTML = renderSkeletons();
var piBlock = res$("price-insight");
if (piBlock) piBlock.innerHTML = "";
if (countEl) countEl.textContent = S.fetching || "Fetching results…";
var apiTrip =
state.tripType === "one_way" ? "one_way" : "round_trip";
var params = new URLSearchParams({
engine: "google_flights",
api_key: API_KEY,
departure_id: state.from,
arrival_id: state.to,
outbound_date: state.departDate,
flight_type: apiTrip,
travel_class: state.travelClass,
adults: state.adults,
stops: state.stops,
sort_by: apiSortBy(),
currency: state.currency,
gl: "in",
hl: "en",
});
if (apiTrip === "round_trip" && state.returnDate) {
params.set("return_date", state.returnDate);
}
try {
var data;
if (useRestProxy) {
var payload = {
departure_id: state.from,
arrival_id: state.to,
outbound_date: state.departDate,
flight_type: apiTrip,
travel_class: state.travelClass,
adults: parseInt(state.adults, 10) || 1,
children: parseInt(state.children, 10) || 0,
stops: "any",
sort_by: apiSortBy(),
currency: state.currency,
gl: "in",
hl: "en",
};
if (apiTrip === "round_trip" && state.returnDate) {
payload.return_date = state.returnDate;
}
var res = await fetch(REST_URL, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "same-origin",
body: JSON.stringify(payload),
});
var rawText = await res.text();
if (!res.ok) {
var errObj = {};
try {
errObj = JSON.parse(rawText);
} catch (e) {}
var msg =
(errObj && errObj.message) ||
(errObj && errObj.data && errObj.data.message) ||
"API error " + res.status;
throw new Error(String(msg));
}
try {
data = JSON.parse(rawText);
} catch (e2) {
throw new Error("Invalid JSON from server");
}
} else {
var resLegacy = await fetch(BASE_URL + "?" + params.toString());
if (!resLegacy.ok) throw new Error("API error " + resLegacy.status);
data = await resLegacy.json();
}
handleResults(data);
} catch (err) {
if (list) {
list.innerHTML =
'' +
"" +
(S.couldNotLoad || "Could not load flights") +
" " +
escapeHtml(err.message || String(err)) +
"
";
}
if (countEl) countEl.textContent = S.errorCount || "Error loading results";
}
finally {
if (loading) loading.hidden = true;
}
}
function resolveFieldToIata(el) {
if (!el) return null;
var selected = el.getAttribute("data-selected-code");
if (selected && /^[A-Z]{3}$/.test(selected)) return selected;
var raw = String(el.value || "").trim();
if (!raw) return null;
return null;
}
function doSearch() {
if (state.tripType === "multi_city") return;
if (!isHome) return;
var fe = $("travelgo-ss-from-input");
var te = $("travelgo-ss-to-input");
var fc = resolveFieldToIata(fe);
var tc = resolveFieldToIata(te);
if (fc) updateAirport("from", fc);
if (tc) updateAirport("to", tc);
if (!state.from || !state.to || !state.departDate) return;
if (state.tripType === "round_trip" && !state.returnDate) return;
if (!fc || !tc) {
window.alert(
S.airportInvalid ||
"Please select origin and destination from the airport dropdown."
);
return;
}
if (!RESULTS_PAGE_URL) {
window.alert(
S.configureResultsPage ||
"Choose a flight results page under Appearance → Customize → Flights."
);
return;
}
var sep = RESULTS_PAGE_URL.indexOf("?") >= 0 ? "&" : "?";
window.location.href = RESULTS_PAGE_URL + sep + buildTfQuery();
}
if (isHome && homeRoot) {
var searchBtn = $("travelgo-ss-search");
if (searchBtn) searchBtn.addEventListener("click", doSearch);
initDates();
updateAirport("from", "DEL");
updateAirport("to", "BOM");
updatePaxSummary();
setTripType("one_way");
}
if (isResults) {
var resCur = document.getElementById("travelgo-fr-currency-select");
if (resCur) {
resCur.value = state.currency;
resCur.addEventListener("change", function () {
state.currency = normalizeCurrency(resCur.value);
syncResultsUrl();
fetchFlightData();
renderFareCalendar();
});
}
wireResultsFilters();
initResultsFromUrl();
}
})();